﻿namespace LiteBlog.Repositories
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Xml.Linq;

    using LiteBlog.Common;
    using LiteBlog.Common.Components;
    using LiteBlog.Common.Entities;
    using LiteBlog.Common.Enums;
    using LiteBlog.Repositories.Contracts;
    using LiteBlog.Repositories.Helpers;
    using LiteBlog.Repositories.Framework;
    using LiteBlog.Repositories.Common;

    public class BlogRepository : DataAccess, IBlogRepository
    {
        private DraftRepository _draftData = null;

        public BlogRepository() : base(null, null) { } // for Moq

        public BlogRepository(IDataContext context, IXmlHelper xmlHelper)
            : base(context, xmlHelper)
        {
            _path = context.GetPath(DataStoreType.Blog, null);
            _draftData = new DraftRepository(context, xmlHelper);
        }

        public void ChangeAuthor(string oldAuthor, string newAuthor)
        {
            var root = _xmlHelper.Load(_path);
            var postsToUpdate = root.Elements(Elem.Post)
                                    .Where(elem => 
                                        elem.Attribute(Attr.Author)
                                            .Value
                                            .Equals(oldAuthor));
            foreach (var postToUpdate in postsToUpdate)
            {
                postToUpdate.SetAttributeValue(Attr.Author, newAuthor);
            }
            _xmlHelper.Save(root, _path);
        }

        public void ChangeCategory(string oldCatID, string newCatID)
        {
            var root = _xmlHelper.Load(_path);
            bool found = false;

            foreach (var postElem in root.Elements(Elem.Post))
            {
                string[] ids = postElem.Attribute(Attr.CatId)
                                       .Value
                                       .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                int pos = Array.IndexOf(ids, oldCatID);
                if (pos == -1)
                    continue;
                var idList = ids.ToList();
                found = true;
                if (!string.IsNullOrEmpty(newCatID))
                    idList[pos] = newCatID;
                else
                    idList.RemoveAt(pos);
                if (idList.Any())
                {
                    postElem.SetAttributeValue(Attr.CatId, idList.Aggregate((cur, next) => cur + "," + next));
                }
                else
                {
                    postElem.SetAttributeValue(Attr.CatId, string.Empty);
                }
            }

            if (found)
                _xmlHelper.Save(root, _path);
        }

        public void ChangeTag(string oldTagId, string newTagId)
        {
            var root = _xmlHelper.Load(_path);
            bool found = false;

            foreach (var postElem in root.Elements(Elem.Post))
            {
                string[] ids = postElem.Attribute(Attr.TagId)
                                       .Value
                                       .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                int pos = Array.IndexOf(ids, oldTagId);
                if (pos == -1)
                {
                    continue;
                }
                var idList = ids.ToList();
                found = true;
                if (!string.IsNullOrEmpty(newTagId))
                {
                    idList[pos] = newTagId;
                }
                else
                {
                    idList.RemoveAt(pos);
                }
                var tagIds = string.Join(",", idList);
                postElem.SetAttributeValue(Attr.TagId, tagIds);
            }

            if (found)
            {
                _xmlHelper.Save(root, _path);
            }
        }

        internal virtual void ChangeViews(Dictionary<string, PostInfo> pageVisits)
        {
            var root = _xmlHelper.Load(_path);
            foreach (KeyValuePair<string, PostInfo> kvp in pageVisits)
            {
                var postElem = root.Elements(Elem.Post)
                                   .SingleOrDefault(elem => 
                                       elem.Attribute(Attr.FileId)
                                           .Value
                                           .Equals(kvp.Key));
                if (postElem != null)
                {
                    postElem.SetAttributeValue(Attr.Views, kvp.Value.Views);
                }
            }
            _xmlHelper.Save(root, _path);
        }

        internal void Create(PostInfo postInfo)
        {
            var root = _xmlHelper.Load(_path);
            var postElem = root.Elements(Elem.Post)
                               .SingleOrDefault(elem => 
                                   elem.Attribute(Attr.FileId)
                                       .Value
                                       .Equals(postInfo.FileID));

            if (postElem == null)
            {
                string catIds = string.Empty;
                if (postInfo.Categories != null && postInfo.Categories.Count > 0)
                    catIds = postInfo.Categories.Select(c => c.CatID).Aggregate((cur, next) => cur + "," + next);

                string tagIds = string.Empty;
                if (postInfo.Tags != null && postInfo.Tags.Count() > 0)
                    tagIds = postInfo.Tags.Select(t => t.TagId).Aggregate((cur, next) => cur + "," + next);

                postElem = new XElement(
                Elem.Post,
                new XAttribute(Attr.Title, postInfo.Title),
                new XAttribute(Attr.FileId, postInfo.FileID),
                new XAttribute(Attr.Author, postInfo.Author),
                new XAttribute(Attr.CatId, catIds),
                new XAttribute(Attr.TagId, tagIds),
                new XAttribute(Attr.MonthId, postInfo.MonthId),
                new XAttribute(Attr.Time, postInfo.Time.ToString(Constants.DateTimeFormat, CultureInfo.InvariantCulture)),
                new XAttribute(Attr.UpdatedTime, postInfo.UpdatedTime.ToString(Constants.DateTimeFormat, CultureInfo.InvariantCulture)),
                new XAttribute(Attr.Views, 1));

                root.Add(postElem);
                Sort(root);
                _xmlHelper.Save(root, _path);
            }
        }

        internal void Delete(string fileId)
        {
            var root = _xmlHelper.Load(_path);
            var postElem = root.Elements(Elem.Post)
                                .SingleOrDefault(elem => 
                                    elem.Attribute(Attr.FileId).Value.Equals(fileId));
            if (postElem != null)
            {
                postElem.Remove();
                _xmlHelper.Save(root, _path);
            }
        }

        internal void Update(PostInfo postInfo)
        {
            if (postInfo.Type == PostType.Draft)
            {
                _draftData.Update(postInfo);
                return;
            }

            var root = _xmlHelper.Load(_path);
            var postElem = root.Elements(Elem.Post)
                               .Single(elem => 
                                   elem.Attribute(Attr.FileId)
                                       .Value
                                       .Equals(postInfo.FileID));
            postElem.SetAttributeValue(Attr.Title, postInfo.Title);
            string catIds = string.Empty;
            if (postInfo.Categories != null && postInfo.Categories.Count > 0)
                catIds = postInfo.Categories.Select(c => c.CatID).Aggregate((cur, next) => cur + "," + next);
            postElem.SetAttributeValue(Attr.CatId, catIds);
            string tagIds = string.Empty;
            if (postInfo.Tags != null && postInfo.Tags.Count() > 0)
                tagIds = postInfo.Tags.Select(t => t.TagId).Aggregate((cur, next) => cur + "," + next);
            postElem.SetAttributeValue(Attr.TagId, tagIds);
            postElem.SetAttributeValue(Attr.Author, postInfo.Author);
            postElem.SetAttributeValue(Attr.UpdatedTime, postInfo.UpdatedTime.ToString(Constants.DateTimeFormat, CultureInfo.InvariantCulture));
            var savedTime = (string)postElem.Attribute(Attr.Time);
            var updateTime = postInfo.Time.ToString(Constants.DateTimeFormat, CultureInfo.InvariantCulture);
            if (savedTime != updateTime)
            {
                postElem.SetAttributeValue(Attr.Time, updateTime);
                Sort(root);
            }
            _xmlHelper.Save(root, _path);
        }

        private void Sort(XElement root)
        {
            var postElems = root.Elements(Elem.Post)
                                .OrderByDescending(elem => 
                                    DateTime.ParseExact((string)elem.Attribute(Attr.Time), Constants.DateTimeFormat, null))
                                .ToList();
            root.RemoveNodes();
            foreach (var postElem in postElems)
                root.Add(postElem);
        }

        internal bool PostExists(string fileId)
        {
            var root = _xmlHelper.Load(_path);
            return root.Elements(Elem.Post)
                        .SingleOrDefault(elem => (string)elem.Attribute(Attr.FileId) == fileId)
                        != null;
        }

        public virtual List<PostInfo> GetPublished()
        {
            var postInfos = new List<PostInfo>();
            var root = _xmlHelper.Load(_path);

            foreach (var postElem in root.Elements(Elem.Post))
            {
                var postInfo = new PostInfo();
                postInfo.Title = postElem.Attribute(Attr.Title).Value;
                postInfo.Author = postElem.Attribute(Attr.Author).Value;
                postInfo.FileID = postElem.Attribute(Attr.FileId).Value;
                var csvIds = postElem.Attribute(Attr.CatId).Value;
                var categoryData = new CategoryRepository(_context, _xmlHelper);
                postInfo.Categories = categoryData.GetCategories(csvIds);
                var tagAttr = postElem.Attribute(Attr.TagId);
                if (tagAttr != null)
                {
                    csvIds = tagAttr.Value;
                    var tagData = new TagRepository(_context, _xmlHelper);
                    postInfo.Tags = tagData.GetTags(csvIds);
                }
                postInfo.Time = DateTime.ParseExact(
                        postElem.Attribute(Attr.Time).Value, Constants.DateTimeFormat, CultureInfo.InvariantCulture);
                var updatedTimeAttr = postElem.Attribute(Attr.UpdatedTime);
                if (updatedTimeAttr != null)
                {
                    postInfo.UpdatedTime = DateTime.ParseExact(
                        updatedTimeAttr.Value, Constants.DateTimeFormat, CultureInfo.InvariantCulture);
                }
                postInfo.Views = int.Parse(postElem.Attribute(Attr.Views).Value);
                postInfo.Type = PostType.Published;
                postInfos.Add(postInfo);
            }

            return postInfos;
        }

        public List<PostInfo> Get()
        {
            var postInfos = GetPublished();
            var draftPostInfos = _draftData.Get();
            postInfos.AddRange(draftPostInfos);
            return postInfos.OrderByDescending(pi => pi.Time).ToList();
        }

        public void Schedule(string fileId, DateTime publishDate)
        {
            _draftData.Schedule(fileId, publishDate);
        }

        public List<PostInfo> GetScheduled()
        {
            return _draftData.GetScheduled();
        }
    }
}